/**
 * Codigo de exemplo para aula de OpenGL
 * Disciplina: Computacao Grafica
 * ICMC - USP Sao Carlos
 * Abril 2013
 *
 * Alceu Costa - alceufc@icmc.usp.br
 */

#include <math.h>
#include <vector>

#include "Camera.h"

Camera::Camera(GLdouble posX, GLdouble posY, GLdouble posZ,
        GLdouble lookX, GLdouble lookY, GLdouble lookZ) {

    this->posX = posX;
    this->posY = posY;
    this->posZ = posZ;

    // Calcula o vetor unitário de direção.
    GLdouble lookNorm = sqrt(lookX * lookX + lookY * lookY + lookZ * lookZ);
    lookX /= lookNorm;
    lookY /= lookNorm;
    lookZ /= lookNorm;

    this->centerX = posX + lookX;
    this->centerY = posY + lookY;
    this->centerZ = posZ + lookZ;

    this->fustrumNearPlane = 1.0f;
    this->fustrumFarPlane = 100000.0f;
//    this->fustrumNearPlane = 10.0f;
//    this->fustrumFarPlane = 800.0f;

    this->mouseSensitivityX = M_PI * 2;
    this->mouseSensitivityY = M_PI_2;

    this->orthogonal = false;
}

void Camera::moveFront(GLdouble dist) {
    // Calcula o vetor unitário de direção.
    GLdouble lookX = this->centerX - this->posX;
    GLdouble lookY = this->centerY - this->posY;
    GLdouble lookZ = this->centerZ - this->posZ;

    // Move a câmera na direção do vetor de direção.
    this->posX += lookX * dist;
    this->posY += lookY * dist;
    this->posZ += lookZ * dist;

    this->centerX += lookX * dist;
    this->centerY += lookY * dist;
    this->centerZ += lookZ * dist;
}

void Camera::cameraVision() {
    static GLfloat factor = 1.0f;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    if (orthogonal) {
        glOrtho(-SIDE_SIZE * factor / 2, SIDE_SIZE * factor / 2, -SIDE_SIZE * factor / 2, SIDE_SIZE * factor / 2, this->fustrumNearPlane, this->fustrumFarPlane);
    } else {
        gluPerspective(90.0f, (GLfloat) SIDE_SIZE / (GLfloat) SIDE_SIZE, this->fustrumNearPlane, this->fustrumFarPlane);
    }

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    gluLookAt(this->posX, this->posY, this->posZ,
            this->centerX, this->centerY, this->centerZ,
            0.0, 1.0, 0.0);

}

void Camera::strafe(GLdouble dist) {
    GLdouble lookX = this->centerX - this->posX;
    GLdouble lookZ = this->centerZ - this->posZ;

    // As formulas abaixo sao obtidas pelo produto vetorial entre o vetor look e o vetor (0,1,0).
    GLdouble strafeX = -lookZ;
    // GLdouble strafeY = 0.0; -- Nao precisa calular.
    GLdouble strafeZ = lookX;
    GLdouble strafeNorm = sqrt(lookX * lookX + lookZ * lookZ);

    strafeX /= strafeNorm;
    strafeZ /= strafeNorm;

    this->posX += strafeX * dist;
    this->posZ += strafeZ * dist;

    this->centerX += strafeX * dist;
    this->centerZ += strafeZ * dist;
}

void Camera::updateLook(GLdouble distFromCenterX, GLdouble distFromCenterY) {
    // Converte as coordenadas do mouse para inclinacao a azimute.
    GLdouble azimuth = -distFromCenterX * this->mouseSensitivityX;
    GLdouble inclination = M_PI_2 + distFromCenterY * this->mouseSensitivityY;

    if (inclination > M_PI) {
        inclination = M_PI;
    } else if (inclination < -M_PI_2) {
        inclination = -M_PI_2;
    }

    this->rotateCamera(inclination, azimuth);
}

void Camera::rotateCamera(GLdouble inclination, GLdouble azimuth) {
    GLdouble lookX = sin(inclination) * sin(azimuth);
    GLdouble lookY = cos(inclination);
    GLdouble lookZ = sin(inclination) * cos(azimuth);

    this->centerX = this->posX + lookX;
    this->centerY = this->posY + lookY;
    this->centerZ = this->posZ + lookZ;

    //    glutPostRedisplay();
}

void Camera::controlOfTranslation(int key, int x, int y) {

    switch (key) {
        case GLUT_KEY_UP:
            this->moveFront(MOVE_STEP);
            break;
        case GLUT_KEY_DOWN:
            this->moveFront(-MOVE_STEP);
            break;
        case GLUT_KEY_LEFT:
            this->strafe(-MOVE_STEP);
            break;
        case GLUT_KEY_RIGHT:
            this->strafe(MOVE_STEP);
            break;
    }
}